package alfabankapi

import (
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/youmark/pkcs8"
)

//* `GetAuthorizationCode()` (generate and return an authorization code)

// https://developers.alfabank.ru/products/alfa-api/documentation/articles/alfa-id/articles/acf-h2h/articles/get-auth-code/v1/get-auth-code?navFilter=h2h
func (c *ApiClient) GetAuthCodeUrl() string {
	if c.URL == "https://sandbox.alfabank.ru" {
		return fmt.Sprintf("https://id-sandbox.alfabank.ru/oidc/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=%s",
			c.ClientID, c.RedirectURI, c.Scope, c.State)
	} else {
		return fmt.Sprintf("https://id.alfabank.ru/oidc/authorize?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=%s",
			c.ClientID, c.RedirectURI, c.Scope, c.State)
	}
}

// https://developers.alfabank.ru/products/alfa-api/documentation/articles/connection/articles/clientsecret/clientsecret?navFilter=h2h
func (c *ApiClient) GetClientSecret() (string, error) {
	if c.clientSecret != "" {
		return c.clientSecret, nil
	}

	// Try to read the client secret from the file
	if c.clientSecretFile != "" {
		data, err := os.ReadFile(c.clientSecretFile)
		if err != nil {
			if !os.IsNotExist(err) {
				return "", err
			}
		} else {
			c.clientSecret = string(data)
			return c.clientSecret, nil
		}
	}

	// If not available in the file, retrieve it from the API
	url := fmt.Sprintf("%s/oidc/clients/%s/client-secret", c.URL, c.ClientID)
	headers := map[string]string{
		"Accept": "application/json",
	}
	body := []byte{}
	resp, err := c.makeRequest("POST", url, headers, body)
	if err != nil {
		return "", err
	}
	var response struct {
		ClientID     string `json:"clientId"`
		ClientSecret string `json:"clientSecret"`
	}
	err = json.Unmarshal(resp, &response)
	if err != nil {
		return "", err
	}
	c.clientSecret = response.ClientSecret

	// Store the client secret in the file
	if c.clientSecretFile != "" {
		err = os.WriteFile(c.clientSecretFile, []byte(c.clientSecret), 0600)
		if err != nil {
			log.Println("Failed to write client secret to file:", err)
		}
	}

	return c.clientSecret, nil
}

// function to load the TLS certificate and key
func loadTLSConfig(certFile, keyFile, password, caFile string, ifSandBox bool) (tlsConfig *tls.Config, err error) {
	certPEM, err := os.ReadFile(certFile)
	if err != nil {
		return nil, errors.New("failed to read certificate file: " + err.Error())
	}

	keyPEM, err := os.ReadFile(keyFile)
	if err != nil {
		return nil, errors.New("failed to read private key file: " + err.Error())
	}

	// Decrypt the private key if it is encrypted
	if ifSandBox == true {
		keyPEM, err = decryptPrivateKeySand(keyPEM, []byte(password))
		if err != nil {
			return nil, errors.New("failed to decrypt private key: " + err.Error())
		}
		cert, err := tls.X509KeyPair(certPEM, keyPEM)
		if err != nil {
			return nil, errors.New("failed to parse certificate and private key: " + err.Error())
		}

		caPEM, err := os.ReadFile(caFile)
		if err != nil {
			return nil, errors.New("failed to read CA certificate file: " + err.Error())
		}

		caCertPool := x509.NewCertPool()
		for {
			var block *pem.Block
			block, caPEM = pem.Decode(caPEM)
			if block == nil {
				break
			}

			log.Println("Added CA certificate")
			caCert, err := x509.ParseCertificate(block.Bytes)
			if err != nil {
				return nil, errors.New("failed to parse CA certificate: " + err.Error())
			}
			caCertPool.AddCert(caCert)
		}

		// Create the TLS config
		tlsConfig = &tls.Config{
			Certificates: []tls.Certificate{cert},
			RootCAs:      caCertPool,
			VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
				log.Println("Verified chains:", verifiedChains)
				return nil
			},
		}
	} else {
		keyPEM, err = decryptPrivateKey(keyPEM, []byte(password))
		if err != nil {
			return nil, errors.New("failed to decrypt private key: " + err.Error())
		}
		cert, err := tls.X509KeyPair(certPEM, keyPEM)
		if err != nil {
			return nil, errors.New("failed to parse certificate and private key: " + err.Error())
		}

		// Create the TLS config
		tlsConfig = &tls.Config{
			Certificates: []tls.Certificate{cert},
		}
	}

	return tlsConfig, nil
}

func decryptPrivateKeySand(keyPEM []byte, password []byte) ([]byte, error) {
	block, _ := pem.Decode(keyPEM)
	if block == nil {
		return nil, errors.New("failed to decode PEM block containing private key")
	}

	if x509.IsEncryptedPEMBlock(block) {
		decryptedKey, err := x509.DecryptPEMBlock(block, password)
		if err != nil {
			return nil, err
		}
		return pem.EncodeToMemory(&pem.Block{
			Type:  "PRIVATE KEY",
			Bytes: decryptedKey,
		}), nil
	}

	return keyPEM, nil
}

func decryptPrivateKey(keyPEM []byte, password []byte) ([]byte, error) {
	block, _ := pem.Decode(keyPEM)
	if block == nil {
		return nil, errors.New("failed to decode PEM block containing private key")
	}

	if x509.IsEncryptedPEMBlock(block) {
		var err error
		block.Bytes, err = x509.DecryptPEMBlock(block, password)
		if err != nil {
			return nil, err
		}
	}

	// Convert the decrypted PEM block to a PKCS#8 private key
	privateKey, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, password)
	if err != nil {
		return nil, err
	}

	// Marshal the private key to PEM
	privateKeyPEM := &pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: MarshalPKCS8PrivateKey(privateKey),
	}

	return pem.EncodeToMemory(privateKeyPEM), nil
}

func MarshalPKCS8PrivateKey(key interface{}) []byte {
	var privateKeyBytes []byte
	var err error

	switch k := key.(type) {
	case *rsa.PrivateKey:
		privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(k)
	case *ecdsa.PrivateKey:
		privateKeyBytes, err = x509.MarshalPKCS8PrivateKey(k)
	default:
		err = errors.New("unsupported private key type")
	}

	if err != nil {
		return nil
	}

	return privateKeyBytes
}
